home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2410 / 2410.xpi / chrome / content / foxmarks-rdf.js < prev    next >
Text File  |  2010-01-28  |  30KB  |  809 lines

  1. /*
  2.  Copyright 2007-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-rdf.js: implements class BookmarkDatasource, encapsulating
  5.  the Firefox RDF datasource.
  6.  
  7.  */
  8.  
  9. // To do:
  10. // * icons in 1.0.1: whoops!
  11. // * fix (ahem) exception text
  12. // * figure out how to move helper mapping functions back into prototype
  13. // * Never set a date backward in time.
  14.  
  15.  
  16. var Cc = Components.classes;
  17. var Ci = Components.interfaces;
  18. const RDF_ROOT = "NC:BookmarksRoot";
  19. const NC = "http://home.netscape.com/NC-rdf#";
  20. const NS = "http://home.netscape.com/WEB-rdf#";
  21. const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  22.  
  23. function ParseIconString(s) {
  24.     var exp = /^data:(.*);base64,(.*)$/;
  25.     var match = exp.exec(s);
  26.  
  27.     if (match) {
  28.         try {
  29.             var data = My_atob(match[2]);
  30.         } catch (e) {
  31.             return null;
  32.         }
  33.         return [match[1] /* mime type */, data /* b64-encoded data */];
  34.     } else {
  35.         return null;
  36.     }
  37. }
  38.  
  39.        
  40. // Node's representation of dates is seconds since 1/1/1970.
  41. // RDF's representation of dates is microseconds since 1/1/1970.
  42. // Use these utility functions to convert between the two formats
  43. // while minimizing rounding error.
  44.  
  45. function DateRDFToNode(v) {
  46.     return Math.round(v / 1000000);
  47. }
  48.  
  49. function DateNodeToRDF(v) {
  50.     return v * 1000000;
  51. }
  52.  
  53. const MAP_RDF_TYPE = {
  54.     "http://home.netscape.com/NC-rdf#Bookmark"          : "bookmark",
  55.     "http://home.netscape.com/NC-rdf#Folder"            : "folder",
  56.     "http://home.netscape.com/NC-rdf#BookmarkSeparator" : "separator",
  57.     "http://home.netscape.com/NC-rdf#Livemark"          : "feed",
  58.     "http://home.netscape.com/NC-rdf#MicsumBookmark"    : "microsummary"
  59. }
  60.  
  61. const MAP_NODE_TYPE = {
  62.     "bookmark"      : null,             // special case: bookmarks & folders 
  63.     "folder"        : null,             // have their types inferred
  64.     "separator"     : NC + "BookmarkSeparator",
  65.     "feed"          : NC + "Livemark",
  66.     "microsummary"  : NC + "MicsumBookmark"
  67. };
  68.  
  69. // Node type requires special mapping
  70. function MapRDFType(node, resource, predicate, target) {
  71.     if (target instanceof Ci.nsIRDFResource) {
  72.         node.ntype = MAP_RDF_TYPE[target.Value];
  73.     } else {
  74.         throw Error("Whatever! I do what I want!");
  75.     }
  76. };
  77.  
  78.  
  79. // Returns either:
  80. // 1) array of [pred, targ, targtype (literal vs. resource)]
  81. // 2) null (skip it)
  82. // 3) string (reset incoming attribute to this type and skip assertion)
  83.  
  84. function MapNodeType(ntype) {
  85.     if (ntype in MAP_NODE_TYPE) {
  86.         var map = MAP_NODE_TYPE[ntype];
  87.         if (map) {
  88.             return [RDF + "type", map, true];
  89.         } else {
  90.             return null;
  91.         }
  92.     } else {
  93.         return "bookmark";
  94.     }
  95. }
  96.  
  97. // Sidebar requires special mapping, as it's stored in RDF as a literal
  98. // but we want to present as boolean in our node structure.
  99. function MapRDFSidebar(node, resource, predicate, target) {
  100.     node.sidebar = true;
  101. }
  102.  
  103. function MapNodeSidebar(sidebar) {
  104.     if (sidebar) {
  105.         return [NC + "WebPanel", "true", false];
  106.     } else {
  107.         return [null, null, false];
  108.     }
  109. }
  110.  
  111. function BookmarkDatasource() {
  112.     this.rdfs = Cc["@mozilla.org/rdf/rdf-service;1"].getService(
  113.       Ci.nsIRDFService);
  114.     
  115.     this.ds = this.rdfs.GetDataSource("rdf:bookmarks").
  116.             QueryInterface(Ci.nsIBookmarksService);
  117.  
  118.     this.container = Cc["@mozilla.org/rdf/container;1"].
  119.       createInstance(Ci.nsIRDFContainer);
  120.     this.rdfcu = Cc["@mozilla.org/rdf/container-utils;1"].
  121.       getService(Ci.nsIRDFContainerUtils);
  122.     this.lastModifiedPredicate = this.rdfs.
  123.       GetResource("http://home.netscape.com/WEB-rdf#LastModifiedDate");
  124.     this.addDatePredicate = this.rdfs.
  125.       GetResource("http://home.netscape.com/NC-rdf#BookmarkAddDate");
  126.     this.typePredicate = this.rdfs.
  127.       GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
  128.     if (!BookmarkDatasource._mapNidToNative) {
  129.         this._InitNidNativeMaps();
  130.     }
  131.  
  132.     applyCommonBookmarkFunctions(this);
  133.  
  134. }
  135.  
  136. // For backwards compatibility, this must be an empty string
  137. BookmarkDatasource.STORAGE_ENGINE = "";
  138.  
  139. BookmarkDatasource.startupTime = Date.now()
  140.  
  141. BookmarkDatasource.ProvideNodesDone = function(status) {
  142.     var tb = this.GetToolbar();
  143.     if (tb) {
  144.         this.pn.Caller.Node(NODE_ROOT, true).tnid = tb;
  145.     }
  146.     this.pn.Complete.apply(this.pn.Caller, [status]);
  147.     this.pn.Caller.rdfSource = true;
  148.     this.pn = null;
  149.     return;
  150. }
  151.  
  152. BookmarkDatasource.MapRDFToNode = function(resource, pnid) {
  153.     var nid = this.MapNative(resource.Value);
  154.     var node = new Node(nid);
  155.     var predicates = this.ds.ArcLabelsOut(resource);
  156.     node.pnid = pnid;
  157.  
  158.     while (predicates.hasMoreElements()) {
  159.         var predicate = predicates.getNext();
  160.  
  161.         if (this.rdfcu.IsOrdinalProperty(predicate))
  162.             continue;
  163.  
  164.         var target = this.ds.GetTarget(resource, predicate, true);
  165.  
  166.         // sometimes, targets don't exist (icons in particular)
  167.         // just skip 'em
  168.         if (!target)
  169.             continue;
  170.  
  171.         predicate.QueryInterface(Ci.nsIRDFResource);
  172.         target.QueryInterface(Ci.nsIRDFNode);
  173.  
  174.         var map = this.RDF_NODE_MAP[predicate.Value];
  175.         var value = null;
  176.  
  177.         if (map === null) {
  178.             // do nothing
  179.         } else if (typeof(map) == "string") {
  180.             if (target instanceof Ci.nsIRDFResource) {
  181.                 value = String(target.Value);
  182.             } else if (target instanceof Ci.nsIRDFLiteral) {
  183.                 value = String(target.Value);
  184.             } else if (target instanceof Ci.nsIRDFDate) {
  185.                 value = parseInt(target.Value / 1000000.0 + 0.5);
  186.             } else {
  187.                 throw "Erggh! unknown target " + predicate.Value + 
  188.                     "/" + target.Value;
  189.             }
  190.             node[map] = value;
  191.         } else if (typeof(map) == "function") {
  192.             map(node, resource, predicate, target);
  193.         } else {
  194.             throw Error(
  195.                 "Damn, woman! You trying to kill me or something?" +
  196.                     predicate.Value + " / " + typeof(map) + 
  197.                     " / " + map);
  198.         }
  199.     }
  200.  
  201.     if (this.IsContainer(resource)) {
  202.         node.ntype = "folder";
  203.         node.children = [];
  204.         this.container.Init(this.ds, resource);
  205.         var children = this.container.GetElements();
  206.         while (children.hasMoreElements()) {
  207.             var child = children.getNext();
  208.             if (child instanceof Ci.nsIRDFResource)
  209.                 node.children.push(this.MapNative(String(child.Value)));
  210.         }
  211.     }
  212.     if (Xmarks.gSettings.disableIconSync) {
  213.         delete node["icon"];
  214.     }
  215.     this.pn.AddNode.apply(this.pn.Caller, [node]);
  216.     return 0;
  217. }
  218.  
  219. // ot is the static object that maintains state for OnTree,
  220. // the tree-walker.
  221.  
  222. var ot = {}
  223. BookmarkDatasource.Continue = {
  224.     notify: function(timer) {
  225.         var self = ot.self;
  226.         var resources = ot.resources;
  227.         var result;
  228.         var s = Date.now();
  229.         while (resources.length > 0 && Date.now() - s < 100) {
  230.             var next = resources.shift();
  231.             var resource = next[0].QueryInterface(Ci.nsIRDFResource);
  232.             var pnid = next[1];
  233.  
  234.             try {
  235.                 result = ot.action.apply(ot.Caller, [resource, pnid]);
  236.             } catch (e) {
  237.                 Xmarks.LogWrite("OnTree error " + e.toSource());
  238.                 result = 3;
  239.             }
  240.  
  241.             if (result)
  242.                 break;
  243.  
  244.             if (self.IsContainer(resource)) {
  245.                 self.container.Init(self.ds, resource);
  246.  
  247.                 var children = self.container.GetElements();
  248.                 var ix = 0;
  249.                 var nid = self.MapNative(resource.Value);
  250.                 while (children.hasMoreElements()) {
  251.                     if (ot.depthfirst) {
  252.                         resources.splice(ix, 0, 
  253.                             [children.getNext(), nid]);
  254.                         ix += 1;
  255.                     } else {
  256.                         resources.push(
  257.                             [children.getNext(), nid]);
  258.                     }
  259.                 }
  260.             }
  261.         }
  262.  
  263.         if (resources.length > 0 && !result) {
  264.             ot.timer.initWithCallback(BookmarkDatasource.Continue, 10,
  265.                 Ci.nsITimer.TYPE_ONE_SHOT);
  266.         } else {
  267.             ot.complete.apply(ot.Caller, [result]);
  268.         }
  269.     }
  270. }
  271.  
  272. BookmarkDatasource.prototype = {
  273.  
  274.     _InitNidNativeMaps: function() {
  275.         BookmarkDatasource._mapNidToNative = {};
  276.         BookmarkDatasource._mapNativeToNid = {};
  277.         this.AddToMap(RDF_ROOT, NODE_ROOT);
  278.     },
  279.  
  280.     MapNid: function(nid) {
  281.         return BookmarkDatasource._mapNidToNative[nid] || "rdf:#$" + nid;
  282.     },
  283.  
  284.     MapNative: function(resourceId) {
  285.         return BookmarkDatasource._mapNativeToNid[resourceId] || 
  286.             (resourceId.slice(0, 6) == "rdf:#$" ? 
  287.                 resourceId.slice(6) : this.AddToMap(resourceId, resourceId));
  288.     },
  289.  
  290.     AddToMap: function(resourceId, nid) {
  291.         BookmarkDatasource._mapNidToNative[nid] = resourceId;
  292.         BookmarkDatasource._mapNativeToNid[resourceId] = nid;
  293.         return nid;
  294.     },
  295.  
  296.     BaselineLoaded: function(baseline, callback) {
  297.         var self = this;
  298.         self._InitNidNativeMaps();
  299.         self.OnTree(this, ScanForSeparators, Done);
  300.         return;
  301.  
  302.         function ScanForSeparators(resource, pnid) {
  303.             if (!IsSeparator(resource)) {
  304.                 return 0;
  305.             }
  306.  
  307.             var parent = baseline.Node(pnid, false, true);
  308.             if (!parent) {
  309.                 return 0;
  310.             }
  311.  
  312.             for (var i = 0; i < parent.children.length; ++i) {
  313.                 var child = baseline.Node(parent.children[i]);
  314.                 if (child.ntype == "separator" && 
  315.                         !BookmarkDatasource._mapNidToNative[child.nid]) {
  316.                     self.AddToMap(String(resource.Value), child.nid);
  317.                     break;
  318.                 }
  319.             }
  320.             return 0;
  321.         }
  322.  
  323.         function Done(status) {
  324.             callback(status);
  325.         }
  326.  
  327.         function IsSeparator(resource) {
  328.             return (self._GetType(resource) == 
  329.                     "http://home.netscape.com/NC-rdf#BookmarkSeparator");
  330.         }
  331.     },
  332.  
  333.     MAP_NODE_RDF: {
  334.         "nid"           : null,
  335.         "pnid"          : null,
  336.         "children"      : null,
  337.         "tnid"          : null,
  338.         "ntype"         : MapNodeType,
  339.         "description"   : [NC + "Description", Ci.nsIRDFLiteral],
  340.         "name"          : [NC + "Name", Ci.nsIRDFLiteral],
  341.         "created"       : [NC + "BookmarkAddDate", Ci.nsIRDFDate],
  342.         "url"           : [NC + "URL", Ci.nsIRDFLiteral],
  343.         "feedurl"       : [NC + "FeedURL", Ci.nsIRDFLiteral],
  344.         "sidebar"       : MapNodeSidebar,
  345.         "shortcuturl"   : [NC + "ShortcutURL", Ci.nsIRDFLiteral],
  346.         "generateduri"  : [NC + "MicsumGenURI", Ci.nsIRDFLiteral],
  347.         "generatedtitle": null,
  348.         "contenttype"   : [NC + "ContentType", Ci.nsIRDFLiteral],
  349.         "msexpires"     : null,
  350.         "icon"          : [NC + "Icon", Ci.nsIRDFLiteral],
  351.         "formdata"      : [NC + "PostData", Ci.nsIRDFLiteral],
  352.         "modified"      : null,
  353.         "visited"       : [NS + "LastVisitDate", Ci.nsIRDFDate]
  354.     },
  355.  
  356.  
  357.     IMMUTABLE_RDF: {
  358.         "http://home.netscape.com/WEB-rdf#LastModifiedDate"     : true,
  359.         "http://home.netscape.com/NC-rdf#BookmarksToolbarFolder": true,
  360.         "http://home.netscape.com/NC-rdf#ID"                    : true,
  361.         "http://developer.mozilla.org/rdf/vocabulary/forward-proxy#forward-proxy"
  362.                                                                 : true,
  363.         "http://home.netscape.com/NC-rdf#LivemarkExpiration"    : true,
  364.         "http://home.netscape.com/WEB-rdf#LastCharset"          : true,
  365.         "http://home.netscape.com/NC-rdf#Icon"                  : true,
  366.         "http://home.netscape.com/NC-rdf#GeneratedTitle"        : true,
  367.         "http://home.netscape.com/NC-rdf#MicsumExpiration"      : true
  368.     },
  369.  
  370.     AcceptNodes: function(ns, callback) {
  371.         var self = this;
  372.         var optimizeOK = ns._cloneSource && ns._cloneSource.rdfSource;
  373.         self.StartWrite();
  374.         ns.OnTree(FlushNode, Done);
  375.         return;
  376.  
  377.         function FlushNode(nid, pnid) {
  378.             if (optimizeOK && !ns._node[nid]) {
  379.                 // We can skip writing this because we know that the
  380.                 // nodeset in question is cloned from a nodeset that
  381.                 // originated with the local datastore, and the node
  382.                 // in question exists only in the clone source; it's
  383.                 // thus unchanged.
  384.                 return 0;
  385.             }
  386.  
  387.             self.WriteNode(ns, ns.Node(nid));
  388.             return 0;
  389.         }
  390.  
  391.         function Done(status) {
  392.             if (status) {
  393.                 callback(status);
  394.             } else {
  395.                 // You might think that we should set the toolbar
  396.                 // before calling EndWrite, but (at least in the case
  397.                 // of Firefox 2), you'd be wrong: due to a repaint bug,
  398.                 // the toolbar ignores changes to the designated toolbar
  399.                 // while it's in the middle of batch updates. One could
  400.                 // argue that this should be handled in SetToolbar().
  401.                 self.EndWrite();
  402.                 self.SetToolbar(ns.Node(NODE_ROOT).tnid);
  403.                 callback(0);
  404.             }
  405.         }
  406.     },
  407.  
  408.     // Call StartWrite before calling WriteNode
  409.     StartWrite: function() {
  410.         this.ds.beginUpdateBatch();
  411.     },
  412.  
  413.     // Call EndWrite after we're done calling WriteNode
  414.     EndWrite: function() {
  415.         this.ds.endUpdateBatch();
  416.     },
  417.  
  418.     WriteNode: function(ns, node) {
  419.         // iterate over node's properties; generate list of
  420.         // prospective assertions
  421.  
  422.         if (node == null) {
  423.             throw Error("node is null");
  424.         }
  425.  
  426.         var assert = [];
  427.         var attrs = node.GetSafeAttrs();
  428.  
  429.         for (var attr in attrs) {
  430.  
  431.             if (!attrs.hasOwnProperty(attr)) {
  432.                 continue;
  433.             }
  434.  
  435.             var map = this.MAP_NODE_RDF[attr];
  436.             var targ = null;
  437.             var pred = null;
  438.             var value = attrs[attr];
  439.  
  440.             if (map === null) {
  441.                 continue;   // do nothing
  442.             } else if (typeof map == "object") {
  443.                 // XXX: Optimization: build these in advance
  444.                 pred = this.rdfs.GetResource(map[0]);
  445.                 var targType = map[1];
  446.  
  447.                 if (targType == Ci.nsIRDFLiteral) {
  448.                     targ = this.rdfs.GetLiteral(value);
  449.                 } else if (targType == Ci.nsIRDFDate) {
  450.                     targ = this.rdfs.GetDateLiteral(DateNodeToRDF(value));
  451.                 } else {
  452.                     throw Error("Knuckle up! " + typeof value);
  453.                 }
  454.             } else if (typeof map == "function") {
  455.                 var a = map(value);
  456.                 if (!a) {
  457.                     continue;
  458.                 } else if (typeof a == 'object') {
  459.                     pred = this.rdfs.GetResource(a[0]);
  460.                     targ = a[2] ? this.rdfs.GetResource(a[1]) :
  461.                                     this.rdfs.GetLiteral(a[1]);
  462.                 } else if (typeof a == "string") {
  463.                     ns.Node(node.nid, true)[attr] = a;
  464.                     Xmarks.LogWrite("Warning: Set " + attr + " for node " + 
  465.                             node.nid + " to " + a);
  466.                     continue;
  467.                 }
  468.             } else {
  469.                 delete ns.Node(node.nid, true)[attr];
  470.                 Xmarks.LogWrite("Warning: dropping unrecognized attribute " + attr);
  471.                 continue;
  472.             }
  473.  
  474.             assert.push([pred, targ]);
  475.         }
  476.  
  477.         var subj = this.rdfs.GetResource(this.MapNid(node.nid));
  478.  
  479.         if (node.ntype == 'folder') {
  480.             // Are we a folder with no children?
  481.             if (node["children"] == null) {
  482.                 node.children = [];
  483.             }
  484.             for (var i = 0; i < node.children.length; ++i) {
  485.                 var pred = this.rdfcu.IndexToOrdinalResource(i + 1);
  486.                 var targ = this.rdfs.GetResource(this.MapNid(node.children[i]));
  487.                 assert.push([pred, targ]);
  488.             }
  489.             assert.push([this.rdfs.GetResource(RDF + "nextVal"),
  490.                 this.rdfs.GetLiteral("" + (1 + node.children.length))]);
  491.             // Since we are not asserting the type of bookmark and folder
  492.             // nodes, we assert instanceof Seq so that Firefox can infer
  493.             // that we're talking about a folder here.
  494.             assert.push([this.rdfs.GetResource(RDF + "instanceOf"),
  495.                 this.rdfs.GetResource(RDF + "Seq")]);
  496.         } else if (node.ntype == 'feed') {
  497.             // This is some code to keep from deleting children of a 
  498.             // Livemark
  499.             var nextVal = this.ds.GetTarget(subj, 
  500.                 this.rdfs.GetResource(RDF + "nextVal"), true);
  501.             if (nextVal) {
  502.                 assert.push([this.rdfs.GetResource(RDF + "nextVal"), nextVal]);
  503.             }
  504.             assert.push([this.rdfs.GetResource(RDF + "instanceOf"),
  505.                 this.rdfs.GetResource(RDF + "Seq")]);
  506.         }
  507.  
  508.         // Iterate over existing assertions. For each assertion we find,
  509.         // either:
  510.         // (1) delete it entirely
  511.         // (2) change it to match the new target value
  512.         // (3) do nothing, because it is perfect as it is
  513.         // In either of the latter two cases, remove the match from 
  514.         // the prospective list.
  515.  
  516.         var alo = this.ds.ArcLabelsOut(subj);
  517.  
  518.         while (alo.hasMoreElements()) {
  519.             var pred = alo.getNext();
  520.             var targ = this.ds.GetTarget(subj, pred, true);
  521.  
  522.             var i = FindPredicate(pred);
  523.             if (i < 0) {
  524.                 // No match; delete it.
  525.                 if (pred instanceof Ci.nsIRDFResource) {
  526.                     // Don't Unassert if:
  527.                     //   1) It's immutable, or
  528.                     //   2) It's a Livemark's ordinal property
  529.                     if (this.IMMUTABLE_RDF[pred.Value] == null && 
  530.                          (node.ntype != 'feed' || 
  531.                              !this.rdfcu.IsOrdinalProperty(pred)) ) { 
  532.                         Xmarks.LogWrite("Unassert("+subj.Value+","+pred.Value+")");
  533.                         this.ds.Unassert(subj, pred, targ, true);
  534.                     } else if (pred.Value == NC + "Icon" && 
  535.                             !Xmarks.gSettings.disableIconSync) {
  536.                         try {
  537.                             this.ds.removeBookmarkIcon(node.url);
  538.                         } catch (e) {
  539.                             Xmarks.LogWrite("removeBookmarkIcon: " + e.toSource());
  540.                         }
  541.                     }
  542.                 } else {
  543.                     throw Error("Awwww, man!");
  544.                 }
  545.             } else {
  546.                 // We matched the predicate. Do we need to change
  547.                 // the target?
  548.                 if (!TargetsMatch(targ, assert[i][1])) {
  549.                     if (this.IMMUTABLE_RDF[pred.Value] == null) {
  550.                         Xmarks.LogWrite("Change("+subj.Value+","+pred.Value+")");
  551.                         this.ds.Change(subj, pred, targ, assert[i][1]);
  552.                         assert.splice(i, 1);
  553.                     }
  554.                 } else {
  555.                     assert.splice(i, 1);
  556.                 }
  557.             }
  558.         }
  559.  
  560.         // execute remaining assertions against datastore
  561.         for (var i = 0; i < assert.length; ++i) {
  562.             var a = assert[i];
  563.             a[0].QueryInterface(Ci.nsIRDFResource);
  564.             a[1].QueryInterface(Ci.nsIRDFNode);
  565.             Xmarks.LogWrite("Asserting " + subj.Value + "," + 
  566.                 a[0].Value + "," + a[1].Value);
  567.             if (a[0].Value == NC + "Icon" &&
  568.                     this.ds instanceof Ci.nsIBookmarksService) {
  569.                 var iconPieces = ParseIconString(a[1].Value);
  570.                 if (iconPieces) {
  571.                     this.ds.updateBookmarkIcon(node["url"], 
  572.                         iconPieces[0], iconPieces[1], iconPieces[1].length);
  573.                 }
  574.             } else {
  575.                 this.ds.Assert(subj, a[0], a[1], true);
  576.             }
  577.         }
  578.  
  579.         function FindPredicate(p) {
  580.             for (i = 0; i < assert.length; ++i) {
  581.                 if (assert[i][0] == p)
  582.                     return i;
  583.             }
  584.             return -1;
  585.         }
  586.  
  587.         function TargetsMatch(t1, t2) {
  588.             if (t1 instanceof Ci.nsIRDFDate) {
  589.                 return (DateRDFToNode(t1.Value) == DateRDFToNode(t2.Value));
  590.             } else {
  591.                 return t1 == t2;
  592.             }
  593.         }
  594.     },
  595.  
  596.     SetToolbar: function(nid) {
  597.         if (this.GetToolbar() != nid) {
  598.             Xmarks.LogWrite("Setting toolbar nid to " + nid);
  599.             this.ds.setBookmarksToolbarFolder(
  600.                 this.rdfs.GetResource(this.MapNid(nid)));
  601.         }
  602.     },
  603.  
  604.     GetToolbar: function() {
  605.         if (this.ds instanceof Ci.nsIBookmarksService) { 
  606.             var tb = this.ds.getBookmarksToolbarFolder();
  607.             return tb ? this.MapNative(tb.Value) : null;
  608.         } else {
  609.             throw "Criminy! It's not an nsIBookmarksService";
  610.         }
  611.     },
  612.  
  613.     // traverses this's bookmarks hierarchy starting with
  614.     // startnode, calling action(node) for each node in the tree,
  615.     // then calling complete() when traversal is done.
  616.     // enforces rules about maximum run times to prevent hanging the UI
  617.     // when traversing large trees or when running on slow CPU's.
  618.     // action() should return 0 to continue, non-zero status to abort.
  619.     // complete() is called with non-zero status if aborted.
  620.     // depthfirst determines tree traversal order
  621.  
  622.  
  623.     OnTree: function(Caller, action, complete, startnode, depthfirst) {
  624.         ot = {}
  625.         ot.self = this;
  626.         ot.Caller = Caller;
  627.         ot.action = action;
  628.         ot.complete = complete;
  629.         ot.startnode = startnode || this.rdfs.GetResource(RDF_ROOT);
  630.         ot.depthfirst = depthfirst;
  631.         ot.resources = [[ot.startnode, null]];
  632.         ot.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  633.         ot.timer.initWithCallback(BookmarkDatasource.Continue, 
  634.             10, Ci.nsITimer.TYPE_ONE_SHOT);
  635.         return;
  636.     },
  637.  
  638.     IsContainer: function(resource)
  639.     {
  640.         if (!this.rdfcu.IsContainer(this.ds, resource))
  641.             return false;
  642.  
  643.         // if it's a Livemark, don't consider it a container, as we don't
  644.         // process its children
  645.         var type = this.ds.GetTarget(resource, this.typePredicate, true);
  646.  
  647.         return (type instanceof Ci.nsIRDFResource &&
  648.             type.Value != "http://home.netscape.com/NC-rdf#Livemark");
  649.     },
  650.  
  651.     RDF_NODE_MAP: {
  652.         "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"   : MapRDFType,
  653.         "http://home.netscape.com/NC-rdf#Description"       : "description",
  654.         "http://home.netscape.com/NC-rdf#Name"              : "name",
  655.         "http://home.netscape.com/NC-rdf#BookmarkAddDate"   : "created",
  656.         "http://home.netscape.com/NC-rdf#URL"               : "url",
  657.         "http://home.netscape.com/NC-rdf#FeedURL"           : "feedurl",
  658.         "http://home.netscape.com/NC-rdf#WebPanel"          : MapRDFSidebar,
  659.         "http://home.netscape.com/NC-rdf#ShortcutURL"       : "shortcuturl",
  660.         "http://home.netscape.com/NC-rdf#LivemarkExpiration": null,
  661.         "http://home.netscape.com/NC-rdf#Icon"              : "icon",
  662.         "http://home.netscape.com/NC-rdf#BookmarksToolbarFolder" 
  663.                                                             : null,
  664.         "http://home.netscape.com/WEB-rdf#LastModifiedDate" : "modified",
  665.         "http://home.netscape.com/WEB-rdf#LastVisitDate"    : "visited",
  666.         "http://home.netscape.com/NC-rdf#MicsumGenURI"      : "generateduri",
  667.         "http://home.netscape.com/NC-rdf#GeneratedTitle"    : null,
  668.         "http://home.netscape.com/NC-rdf#ContentType"       : "contenttype",
  669.         "http://home.netscape.com/NC-rdf#MicsumExpiration"  : null,
  670.         "http://home.netscape.com/NC-rdf#LivemarkLock"      : null,
  671.         "http://home.netscape.com/NC-rdf#PostData"          : "formdata",
  672.         "http://www.w3.org/1999/02/22-rdf-syntax-ns#children"
  673.                                                             : null,
  674.         "http://www.w3.org/1999/02/22-rdf-syntax-ns#parent" : null,
  675.         "http://www.w3.org/1999/02/22-rdf-syntax-ns#instanceOf" 
  676.                                                             : null,
  677.         "http://www.w3.org/1999/02/22-rdf-syntax-ns#nextVal" 
  678.                                                             : null,
  679.         "http://home.netscape.com/NC-rdf#ID"                : null,
  680.         "http://home.netscape.com/WEB-rdf#LastCharset"      : null,
  681.         "http://developer.mozilla.org/rdf/vocabulary/forward-proxy#forward-proxy"
  682.                                                             : null
  683.     },
  684.  
  685.     GenerateNid: function() {
  686.         var resource = this.rdfs.GetAnonymousResource();
  687.         if (resource instanceof Ci.nsIRDFResource) {
  688.             return this.MapNative(resource.Value);
  689.         } else {
  690.             throw Error("Dagnabbit! Couldn't create anonymous resource");
  691.         }
  692.     },
  693.  
  694.     NormalizeUrl: function(url) {
  695.         return url;
  696.     },
  697.  
  698.     ProvideNodes: function(Caller, AddNode, Complete) {
  699.         this.pn = {}
  700.         this.pn.Caller = Caller;
  701.         this.pn.AddNode = AddNode;
  702.         this.pn.Complete = Complete;
  703.         this.OnTree(this, BookmarkDatasource.MapRDFToNode, 
  704.             BookmarkDatasource.ProvideNodesDone);
  705.         return;
  706.     },
  707.  
  708.     WatchForChanges: function() {
  709.         var watcher = new BookmarkWatcher();
  710.         // start observing
  711.         this.ds.AddObserver(watcher);
  712.         return watcher;
  713.     },
  714.     _GetType: function(resource) {
  715.         var type = this.ds.GetTarget(resource, this.typePredicate, true)
  716.         if (type instanceof Ci.nsIRDFResource) {
  717.             return type.Value;
  718.         } else {
  719.             return null; 
  720.         }
  721.     }
  722.  };
  723.  
  724. function BookmarkWatcher(){
  725.     this.rdfs = Cc["@mozilla.org/rdf/rdf-service;1"].getService(
  726.       Ci.nsIRDFService);
  727.     this.ds = this.rdfs.GetDataSource("rdf:bookmarks").
  728.             QueryInterface(Ci.nsIBookmarksService);
  729.     this.typePredicate = this.rdfs.
  730.       GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
  731. }
  732.  
  733. BookmarkWatcher.prototype = {
  734.     lastModified: null,
  735.  
  736.     NotifyObservers: function(lm) {
  737.         // Take in RDF's microseconds since 1970.
  738.         // Output Javascript milliseconds since 1970.
  739.         lm = Math.round(lm / 1000);
  740.         if (!this.lastModified || lm > this.lastModified) {
  741.             this.lastModified = lm;
  742.             var os = Cc["@mozilla.org/observer-service;1"]
  743.                 .getService(Ci.nsIObserverService);
  744.             os.notifyObservers(null, "foxmarks-datasourcechanged",
  745.                 lm + ";bookmarks");
  746.         }
  747.     },
  748.  
  749.     _GetType: function(resource) {
  750.         var type = this.ds.GetTarget(resource, this.typePredicate, true)
  751.         if (type instanceof Ci.nsIRDFResource) {
  752.             return type.Value;
  753.         } else {
  754.             return null; 
  755.         }
  756.     },
  757.  
  758.  
  759.     ////////////////////////////////////////////////////////////////////////////
  760.     //
  761.     // nsIRDFObserver
  762.  
  763.     onAssert: function(dataSource, source, property, target) {
  764.     },
  765.  
  766.     onUnassert: function(dataSource, source, property, target) {
  767.     },
  768.  
  769.     onChange: function(dataSource, source, property, oldTarget, newTarget) {
  770.         var self = this;
  771.  
  772.         var funcIsLivemark = function(resource) {
  773.             return (self._GetType(resource) == 
  774.                     "http://home.netscape.com/NC-rdf#Livemark");
  775.         };
  776.         var funcIsMicsum = function(resource){
  777.             return (self._GetType(resource) == 
  778.                     "http://home.netscape.com/NC-rdf#MicsumBookmark");
  779.         }
  780.  
  781.         if (property.QueryInterface(Components.interfaces.nsIRDFResource).Value
  782.                 == "http://home.netscape.com/WEB-rdf#LastModifiedDate" &&
  783.                 !funcIsLivemark(source) && !funcIsMicsum(source)) {
  784.             self.NotifyObservers(newTarget.
  785.                 QueryInterface(Ci.nsIRDFDate).Value);
  786.         }
  787.     },
  788.  
  789.     onBeginUpdateBatch: function(dataSource) {
  790.     },
  791.  
  792.  
  793.     onEndUpdateBatch: function(dataSource) {
  794.         // Some kind of changes happened. Alas, the bookmark manager
  795.         // has hidden from us exactly what happened. To be safe,
  796.         // assume that something has changed and fire a sync as required.
  797.         // Oh, and ignore notifications in the first 10 seconds after startup. 
  798.         if (Date.now() - BookmarkDatasource.startupTime >= 10000) {
  799.             if (!Xmarks.gSettings.disableDirtyOnBatch) {
  800.                 Xmarks.LogWrite("onEndUpdateBatch()");
  801.                 this.NotifyObservers(Date.now() * 1000);
  802.             } else {
  803.                 Xmarks.LogWrite("ignoring onEndUpdateBatch");
  804.             }
  805.         }
  806.     }
  807. };
  808.  
  809.